/*
    Copyright (C) 2008  Helmut Juskewycz

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.juskewycz.dividere;

import com.juskewycz.dividere.action.IProgressListener;
import com.juskewycz.dividere.action.ProgressStatus;
import com.juskewycz.dividere.core.JoinController;
import com.juskewycz.dividere.core.SplitController;
import java.awt.event.KeyEvent;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.Task;
import org.jdesktop.application.TaskMonitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.Timer;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import org.jdesktop.application.Task.BlockingScope;

/**
 *
 * @version 
 * @author Helmut Juskewycz (helmut -at- juskewycz.com)
 */
public class DividereView extends FrameView {

    private boolean fileSelected;
    private boolean firstFileSelected;

    public DividereView(SingleFrameApplication app) {
	super(app);
	initComponents();

	// status bar initialization - message timeout, idle icon and busy animation, etc
	ResourceMap resourceMap = getResourceMap();
	int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
	messageTimer = new Timer(messageTimeout, new ActionListener() {

	    public void actionPerformed(ActionEvent e) {
		statusMessageLabel.setText("");
	    }
	});
	messageTimer.setRepeats(false);
	int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
	for (int i = 0; i < busyIcons.length; i++) {
	    busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
	}
	busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {

	    public void actionPerformed(ActionEvent e) {
		busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
		statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
	    }
	});
	idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
	statusAnimationLabel.setIcon(idleIcon);
	progressBar.setVisible(false);

	// connecting action tasks to status bar via TaskMonitor
	TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
	taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {

	    public void propertyChange(java.beans.PropertyChangeEvent evt) {
		String propertyName = evt.getPropertyName();
		if ("started".equals(propertyName)) {
		    if (!busyIconTimer.isRunning()) {
			statusAnimationLabel.setIcon(busyIcons[0]);
			busyIconIndex = 0;
			busyIconTimer.start();
		    }
		    progressBar.setVisible(true);
		    progressBar.setIndeterminate(true);
		} else if ("done".equals(propertyName)) {
		    busyIconTimer.stop();
		    statusAnimationLabel.setIcon(idleIcon);
		    progressBar.setVisible(false);
		    progressBar.setValue(0);
		} else if ("message".equals(propertyName)) {
		    String text = (String) (evt.getNewValue());
		    statusMessageLabel.setText((text == null) ? "" : text);
		    messageTimer.restart();
		} else if ("progress".equals(propertyName)) {
		    int value = (Integer) (evt.getNewValue());
		    progressBar.setVisible(true);
		    progressBar.setIndeterminate(false);
		    progressBar.setValue(value);
		}
	    }
	});

	tfFilePath.addKeyListener(new KeyAdapter() {

	    @Override
	    public void keyTyped(KeyEvent e) {
		updateActionEnableStatus();
	    }
	});
    }

    @Action
    public void showAboutBox() {
	if (aboutBox == null) {
	    JFrame mainFrame = DividereApp.getApplication().getMainFrame();
	    aboutBox = new DividereAboutBox(mainFrame);
	    aboutBox.setLocationRelativeTo(mainFrame);
	}
	DividereApp.getApplication().show(aboutBox);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        mainPanel = new javax.swing.JPanel();
        tfFilePath = new javax.swing.JTextField();
        lInputFile = new javax.swing.JLabel();
        bFileChooser = new javax.swing.JButton();
        bSplit = new javax.swing.JButton();
        bJoin = new javax.swing.JButton();
        cbDeleteOriginalFiles = new javax.swing.JCheckBox();
        cbOverwriteFiles = new javax.swing.JCheckBox();
        menuBar = new javax.swing.JMenuBar();
        javax.swing.JMenu fileMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
        javax.swing.JMenu helpMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
        statusPanel = new javax.swing.JPanel();
        statusMessageLabel = new javax.swing.JLabel();
        statusAnimationLabel = new javax.swing.JLabel();
        progressBar = new javax.swing.JProgressBar();
        jFileChooser1 = new javax.swing.JFileChooser();

        mainPanel.setName("mainPanel"); // NOI18N

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(com.juskewycz.dividere.DividereApp.class).getContext().getResourceMap(DividereView.class);
        tfFilePath.setText(resourceMap.getString("tfFilePath.text")); // NOI18N
        tfFilePath.setName("tfFilePath"); // NOI18N

        lInputFile.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        lInputFile.setText(resourceMap.getString("lInputFile.text")); // NOI18N
        lInputFile.setName("lInputFile"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(com.juskewycz.dividere.DividereApp.class).getContext().getActionMap(DividereView.class, this);
        bFileChooser.setAction(actionMap.get("showFileChooser")); // NOI18N
        bFileChooser.setName("bFileChooser"); // NOI18N

        bSplit.setAction(actionMap.get("splitFile")); // NOI18N
        bSplit.setName("bSplit"); // NOI18N

        bJoin.setAction(actionMap.get("joinFile")); // NOI18N
        bJoin.setText(resourceMap.getString("bJoin.text")); // NOI18N
        bJoin.setName("bJoin"); // NOI18N

        cbDeleteOriginalFiles.setText(resourceMap.getString("cbDeleteOriginalFiles.text")); // NOI18N
        cbDeleteOriginalFiles.setName("cbDeleteOriginalFiles"); // NOI18N

        cbOverwriteFiles.setText(resourceMap.getString("cbOverwriteFiles.text")); // NOI18N
        cbOverwriteFiles.setName("cbOverwriteFiles"); // NOI18N

        javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel);
        mainPanel.setLayout(mainPanelLayout);
        mainPanelLayout.setHorizontalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(mainPanelLayout.createSequentialGroup()
                .addGap(16, 16, 16)
                .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(mainPanelLayout.createSequentialGroup()
                        .addComponent(bJoin, javax.swing.GroupLayout.DEFAULT_SIZE, 136, Short.MAX_VALUE)
                        .addGap(18, 18, 18)
                        .addComponent(bSplit, javax.swing.GroupLayout.PREFERRED_SIZE, 136, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(58, 58, 58))
                    .addGroup(mainPanelLayout.createSequentialGroup()
                        .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(cbDeleteOriginalFiles)
                            .addGroup(mainPanelLayout.createSequentialGroup()
                                .addGap(155, 155, 155)
                                .addComponent(cbOverwriteFiles))
                            .addGroup(mainPanelLayout.createSequentialGroup()
                                .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                                    .addComponent(lInputFile, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                    .addComponent(tfFilePath, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 291, Short.MAX_VALUE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(bFileChooser, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)))
                        .addGap(20, 20, 20))))
        );

        mainPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bJoin, bSplit});

        mainPanelLayout.setVerticalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(mainPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(lInputFile, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(mainPanelLayout.createSequentialGroup()
                        .addComponent(bFileChooser)
                        .addContainerGap())
                    .addGroup(mainPanelLayout.createSequentialGroup()
                        .addComponent(tfFilePath, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(18, 18, 18)
                        .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(cbOverwriteFiles)
                            .addComponent(cbDeleteOriginalFiles))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(bJoin, javax.swing.GroupLayout.DEFAULT_SIZE, 84, Short.MAX_VALUE)
                            .addComponent(bSplit, javax.swing.GroupLayout.DEFAULT_SIZE, 84, Short.MAX_VALUE))
                        .addGap(9, 9, 9))))
        );

        mainPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {bJoin, bSplit});

        menuBar.setName("menuBar"); // NOI18N

        fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
        fileMenu.setName("fileMenu"); // NOI18N

        exitMenuItem.setAction(actionMap.get("quitDividere")); // NOI18N
        exitMenuItem.setName("exitMenuItem"); // NOI18N
        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
        helpMenu.setName("helpMenu"); // NOI18N

        aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
        aboutMenuItem.setName("aboutMenuItem"); // NOI18N
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        statusPanel.setName("statusPanel"); // NOI18N

        statusMessageLabel.setName("statusMessageLabel"); // NOI18N

        statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N

        progressBar.setName("progressBar"); // NOI18N

        javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel);
        statusPanel.setLayout(statusPanelLayout);
        statusPanelLayout.setHorizontalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(statusMessageLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 180, Short.MAX_VALUE)
                .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(statusAnimationLabel)
                .addContainerGap())
        );
        statusPanelLayout.setVerticalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, statusPanelLayout.createSequentialGroup()
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(statusMessageLabel)
                    .addComponent(statusAnimationLabel)
                    .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(3, 3, 3))
        );

        jFileChooser1.setName("jFileChooser1"); // NOI18N

        setComponent(mainPanel);
        setMenuBar(menuBar);
        setStatusBar(statusPanel);
    }// </editor-fold>//GEN-END:initComponents
    @Action
    public void showFileChooser() {
	int returnVal = jFileChooser1.showOpenDialog(DividereApp.getApplication().getMainFrame());
	if (returnVal == JFileChooser.APPROVE_OPTION) {
	    File file = jFileChooser1.getSelectedFile();
	    tfFilePath.setText(file.getPath());
	    updateActionEnableStatus();
	}
    }

    @Action(block = BlockingScope.APPLICATION, enabledProperty = "fileSelected")
    public Task splitFile() {
	SplitSizeDialogListener listener = new SplitSizeDialogListener();
	FileSplitSizeDialog sizeDialog = new FileSplitSizeDialog(DividereApp.getApplication().getMainFrame(), true, listener);
	sizeDialog.setVisible(true);
	return new SplitFileTask(getApplication(), listener.splitSize);
    }

    private class SplitFileTask extends org.jdesktop.application.Task<ProgressStatus, Void> {

	private final long splitSize;
	private ProgressStatus status;

	SplitFileTask(org.jdesktop.application.Application app, long splitSize) {
	    super(app);
	    this.splitSize = splitSize;
	}

	@Override
	protected ProgressStatus doInBackground() {
	    if (splitSize != -1) {
		SplitController sc = new SplitController(tfFilePath.getText(), splitSize, cbDeleteOriginalFiles.isSelected(), cbOverwriteFiles.isSelected());
		IProgressListener listener = new IProgressListener() {

		    public void update(ProgressStatus newStatus) {
			message("startMessage", newStatus.getProcessed(), newStatus.getProcessable());
			setProgress(newStatus.getProcessed(), 0, newStatus.getProcessable());
			status = newStatus;
		    }

		    public void error(ProgressStatus errorStatus) {
			//localize message
			if (errorStatus.getMessage() != null) {
			    errorStatus.setMessage(getResourceMap().getString(errorStatus.getMessage()));
			}
			status = errorStatus;
		    }
		    };
		List<IProgressListener> listeners = new ArrayList<IProgressListener>();
		listeners.add(listener);
		sc.split(listeners);
	    }
	    return status;
	}

	@Override
	protected void succeeded(ProgressStatus result) {
	    if (result != null) {
		if (!result.isError()) {
		    message("finishedMessage", getExecutionDuration(TimeUnit.MINUTES), getExecutionDuration(TimeUnit.SECONDS) / 60);
		} else {
		    showErrorDialog(result.getMessage());
		}
	    }
	    super.succeeded(result);
	}
    }

    @Action(block = BlockingScope.APPLICATION, enabledProperty = "firstFileSelected")
    public Task joinFile() {
	JoinFileTask task = new JoinFileTask(getApplication());
	return task;
    }

    private class JoinFileTask extends org.jdesktop.application.Task<ProgressStatus, Void> {

	private ProgressStatus status;

	JoinFileTask(org.jdesktop.application.Application app) {
	    super(app);
	}

	@Override
	protected ProgressStatus doInBackground() {
	    JoinController jc = new JoinController(tfFilePath.getText(), cbDeleteOriginalFiles.isSelected(), cbOverwriteFiles.isSelected());
	    IProgressListener listener = new IProgressListener() {

		public void update(ProgressStatus newStatus) {
		    message("startMessage", newStatus.getProcessed(), newStatus.getProcessable());
		    setProgress(newStatus.getProcessed(), 0, newStatus.getProcessable());
		    status = newStatus;
		}

		public void error(ProgressStatus errorStatus) {
		    //localize message
		    if (errorStatus.getMessage() != null) {
			errorStatus.setMessage(getResourceMap().getString(errorStatus.getMessage()));
		    }
		    status = errorStatus;
		}
	    };
	    List<IProgressListener> listeners = new ArrayList<IProgressListener>();
	    listeners.add(listener);
	    jc.join(listeners);
	    return status;
	}

	@Override
	protected void succeeded(ProgressStatus result) {
	    if (result != null) {
		if (!result.isError()) {
		    message("finishedMessage", getExecutionDuration(TimeUnit.MINUTES), getExecutionDuration(TimeUnit.SECONDS) / 60);
		} else {
		    showErrorDialog(result.getMessage());
		}
	    }
	    super.succeeded(result);
	}
    }

    private void updateActionEnableStatus() {
	boolean isValidFile = tfFilePath.getText() != null && new File(tfFilePath.getText()).exists();
	setFileSelected(isValidFile);
	setFirstFileSelected(isValidFile && tfFilePath.getText().endsWith("1"));
    }

    public boolean isFileSelected() {
	return fileSelected;
    }

    public void setFileSelected(boolean fileSelected) {
	boolean oldValue = this.fileSelected;
	this.fileSelected = fileSelected;
	firePropertyChange("fileSelected", oldValue, this.fileSelected);
    }

    public boolean isFirstFileSelected() {
	return firstFileSelected;
    }

    public void setFirstFileSelected(boolean firstFileSelected) {
	boolean oldValue = this.firstFileSelected;
	this.firstFileSelected = firstFileSelected;
	firePropertyChange("firstFileSelected", oldValue, this.firstFileSelected);
    }

    private void showErrorDialog(String message) {
	JOptionPane.showMessageDialog(getFrame(), message, "Error", JOptionPane.ERROR_MESSAGE);
    }

    @Action
    public void quitDividere() {
	getApplication().exit();
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton bFileChooser;
    private javax.swing.JButton bJoin;
    private javax.swing.JButton bSplit;
    private javax.swing.JCheckBox cbDeleteOriginalFiles;
    private javax.swing.JCheckBox cbOverwriteFiles;
    private javax.swing.JFileChooser jFileChooser1;
    private javax.swing.JLabel lInputFile;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JLabel statusAnimationLabel;
    private javax.swing.JLabel statusMessageLabel;
    private javax.swing.JPanel statusPanel;
    private javax.swing.JTextField tfFilePath;
    // End of variables declaration//GEN-END:variables
    private final Timer messageTimer;
    private final Timer busyIconTimer;
    private final Icon idleIcon;
    private final Icon[] busyIcons = new Icon[15];
    private int busyIconIndex = 0;
    private JDialog aboutBox;
}
